18. Matplotlib库基础

本章概要

  • 学习材料:贷款本金、年利率、贷款年限与等额本息月供公式。
  • 本章任务:运行 lst-matplotlib-basic-calclst-matplotlib-basic-summaryfig-mortgage-repayment,逐月推导月供、本金、利息与剩余本金。
  • 完成后你将得到:月度本金/利息/余额时间序列,以及带单位、图例的还款曲线。
  • 自我检查:逐月检查本金加利息等于月供、末期余额归零;单位、长度或余额要求未满足时先检查原因。
  • 拓展练习:用明确的第二年利率情景按同一公式重算,并比较月供、累计利息与余额轨迹。

引言:可视化的重要性

金融数据可视化在商业决策中扮演关键角色:

  • 发现模式:识别数据中的趋势、周期与异常
  • 沟通结果:图表比数字更直观,更易于被理解
  • 辅助决策:为交易决策提供有力的视觉支撑

Matplotlib 简介

  • Python 中最经典、最流行的绘图库
  • 提供 pyplot 模块,接口类似 MATLAB
  • 灵活度高,可定制图表的每一个元素
  • 是 Seaborn、Pandas 绘图等高级库的底层基础

基础绘图:核心流程

使用 Matplotlib 绘图的标准流程:

  1. 导入库import matplotlib.pyplot as plt
  2. 准备数据:使用 NumPy 或 Pandas 构造数据
  3. 创建画布/子图plt.figure()plt.subplots()
  4. 绘制图形plt.plot(), plt.bar()
  5. 设置元素:标题、轴标签、图例、网格
  6. 显示/保存plt.show()plt.savefig()

运行前预测|平台任务:按揭贷款本息偿还

  • 输入预测:运行前先写出 rnprinciplepay_month 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到“每月的偿还的金额”相关结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务:按揭贷款本息偿还”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务:按揭贷款本息偿还

展开完整代码(投影默认折叠)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 注:numpy_financial包本地未安装,但平台已经内置
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
plt.rcParams['font.family'] = 'SimHei'  # 设置Matplotlib全局参数
plt.rcParams['axes.unicode_minus'] = False  # 设置Matplotlib全局参数
import numpy_financial as npf  # 导入NumPy数值计算库
import numpy as np  # 导入NumPy数值计算库
r=0.05 # 贷款的年利率
n=30 # 贷款的年数
principle=8e6  # 设置期数/数量为8000000
pay_month=npf.ppmt(rate=r/12,per=n*12,nper=n*12,pv=principle,fv=0,when='end') #计算每月支付的本息之和
print('每月的偿还的金额',round(pay_month,2))  # 输出每月的偿还的金额
T_list=np.arange(n*12)+1 #生成一个包含每次还款期限的数组
# 计算每期应还的本金部分
prin_month=npf.ppmt(rate=r/12,per=T_list,nper=n*12,pv=principle,fv=0,when='end')
# 计算每期应还的利息部分
inte_month=npf.ipmt(rate=r/12,per=T_list,nper=n*12,pv=principle,fv=0,when='end')
pay_month_list=pay_month*np.ones_like(prin_month)  # 生成与prin_month等长的每月总还款额数组
fig=plt.figure(figsize=(9,6))  # 创建图形画布
ax=fig.add_subplot(111)  # 添加单个子图占据整个画布
ax.plot(T_list,-pay_month_list,'r-',label=u'每月偿还金额')  # 在子图上绘制曲线
ax.plot(T_list,-prin_month,'m--',label=u'每月偿还本金金额')  # 在子图上绘制曲线
ax.plot(T_list,-inte_month,'b--',label=u'每月偿还利息金额')  # 在子图上绘制曲线
ax.set_xlabel(u'逐次偿还的期限(月)',fontsize=14)  # 设置X轴标签
ax.set_ylabel(u'金额',fontsize=14)  # 设置Y轴标签
ax.legend()  # 设置图例
plt.savefig("1.png")  # 保存图形至文件

任务复盘|平台任务:按揭贷款本息偿还

运行后核对:核对 rnprinciplepay_month 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:把数据替换为一个中国企业或市场序列,改变一个分组或时间窗口;说明图形结论是否改变,以及为何。

按揭贷款参数设定

Listing 1
展开完整代码
# ==================== 导入必要的库 ====================
import matplotlib.pyplot as plt  # Matplotlib的pyplot模块
import numpy as np  # NumPy数值计算库

# ==================== 按揭贷款参数 ====================
# principal:贷款本金(100万元)
principal = 1000000
# rate:年利率(4.9%,典型商业贷款利率)
rate = 0.049
# years:贷款期限(30年)
years = 30

# ==================== 计算月供(等额本息) ====================
# monthly_rate:月利率 = 年利率 / 12
monthly_rate = rate / 12
# n_months:总月数 = 年数 * 12
n_months = years * 12
# 等额本息月供公式:M = P * [r(1+r)^n] / [(1+r)^n - 1]
monthly_payment = principal * (monthly_rate * (1 + monthly_rate)**n_months) / ((1 + monthly_rate)**n_months - 1)

print(f'月供: {monthly_payment:.2f}元')
print(f'总还款: {monthly_payment * n_months:.2f}元')
print(f'总利息: {monthly_payment * n_months - principal:.2f}元')
月供: 5307.27元
总还款: 1910616.19元
总利息: 910616.19元

生成月度摊还表并完成守恒校验

Listing 2
import pandas as pd  # 导入表格库以保存可核对的月度摊还记录
remaining_principal = principal  # 以贷款本金初始化第一期期初余额
monthly_rows = []  # 建立容纳月供、本金、利息与余额的检查记录
for month_number in range(1, n_months + 1):  # 按合同月序逐期更新贷款余额
    monthly_interest = remaining_principal * monthly_rate  # 用期初余额计算当月利息
    is_final_month = month_number == n_months  # 标记末期以清除浮点尾差
    scheduled_principal = monthly_payment - monthly_interest  # 计算计划本金
    monthly_principal = remaining_principal if is_final_month else scheduled_principal  # 末期结清
    remaining_principal = max(0.0, remaining_principal - monthly_principal)  # 更新且约束非负的期末余额
    payment_record = (month_number, monthly_payment, monthly_principal)  # 保存当月付款字段
    balance_record = (monthly_interest, remaining_principal)  # 保存当月余额字段
    monthly_rows.append(payment_record + balance_record)  # 合并当月还款金额与余额字段
schedule_columns = ['月序', '月供', '本金', '利息', '剩余本金']  # 固定月表字段顺序
monthly_schedule = pd.DataFrame(monthly_rows, columns=schedule_columns)  # 形成月度摊还表
monthly_composition = monthly_schedule['本金'] + monthly_schedule['利息']  # 合计本息
assert np.allclose(monthly_composition, monthly_schedule['月供'], atol=1e-6)  # 校验月供构成
is_final_balance_zero = np.isclose(monthly_schedule.iloc[-1]['剩余本金'], 0.0)  # 校验余额
has_full_term = len(monthly_schedule) == n_months  # 校验合同期数
assert is_final_balance_zero and has_full_term  # 同时执行两项核对条件

聚合年度结果并显示检查摘要

Listing 3
monthly_schedule['年序'] = (monthly_schedule['月序'] - 1) // 12 + 1  # 把月序映射为年序以便从同一明细表聚合
annual_group = monthly_schedule.groupby('年序', as_index=False)  # 建立年度分组
annual_schedule = annual_group[['本金', '利息']].sum()  # 聚合年度本金与利息
print(monthly_schedule.head(3).round(2).to_string(index=False))  # 展示首三期以供学生逐行核对
print(monthly_schedule.tail(3).round(2).to_string(index=False))  # 展示末三期以核验余额归零
first_payment = round(monthly_schedule.iloc[0]['月供'], 2)  # 提取首期月供
total_interest = round(monthly_schedule['利息'].sum(), 2)  # 汇总累计利息
final_balance = monthly_schedule.iloc[-1]['剩余本金']  # 提取末期余额
audit_summary = {'月供': first_payment, '累计利息': total_interest}  # 建立摘要主体
audit_summary['末期余额'] = final_balance  # 补入末期核对条件结果
print(audit_summary)  # 输出可与教师答案核对的摘要
 月序      月供      本金      利息      剩余本金  年序
  1 5307.27 1223.93 4083.33 998776.07   1
  2 5307.27 1228.93 4078.34 997547.13   1
  3 5307.27 1233.95 4073.32 996313.18   1
 月序      月供      本金    利息     剩余本金  年序
358 5307.27 5242.78 64.49 10549.87  30
359 5307.27 5264.19 43.08  5285.68  30
360 5307.27 5285.68 21.58     0.00  30
{'月供': 5307.27, '累计利息': 910616.19, '末期余额': 0.0}

绘制按揭贷款本息偿还图

展开实现代码
fig, axes = plt.subplots(1, 2, figsize=(11, 4.2))  # 建立年度构成与月度余额双面板
axes[0].plot(annual_schedule['年序'], annual_schedule['利息'], label='年度利息', linewidth=2)  # 绘制由月表聚合的年度利息
axes[0].plot(annual_schedule['年序'], annual_schedule['本金'], label='年度本金', linewidth=2)  # 绘制由月表聚合的年度本金
axes[1].plot(monthly_schedule['月序'], monthly_schedule['剩余本金'], label='4.9%余额', color='#007A86', linewidth=2)  # 绘制逐月剩余本金轨迹
axes[0].set(xlabel='年序(年)', ylabel='年度金额(元)', title='月表聚合的年度本息')  # 标明年度聚合图的轴、单位与标题
axes[1].set(xlabel='还款月份(月)', ylabel='剩余本金(元)', title='月度余额轨迹')  # 标明余额图的轴、单位与标题
axes[0].legend(fontsize=16)  # 显示年度本息图例
axes[1].legend(fontsize=16)  # 显示利率情景图例
axes[0].grid(True, alpha=0.3)  # 增加年度比较的读数辅助线
axes[1].grid(True, alpha=0.3)  # 增加余额轨迹的读数辅助线
plt.tight_layout()  # 优化双面板间距以避免标签遮挡
plt.show()  # 显示完整核对图形
左图由月度摊还表聚合三十年本金和利息,右图显示同一月度表中从一百万元下降至零的剩余本金。
Figure 1: 月度摊还表聚合的年度本息与月度余额轨迹

扩展导览|常见图表类型(后续章节)

完成本章只检查画布、单轴折线和标签。以下图型是第20章与第54—58章的路线图,不纳入本章核心检查。

图表类型 函数 适用场景
折线图 plt.plot() 时间序列、趋势变化
柱状图 plt.bar() 类别比较
散点图 plt.scatter() 变量关系、相关性
饼图 plt.pie() 占比分布

扩展导览|趋势与类别比较

展开完整代码
import matplotlib.pyplot as plt
import numpy as np

# 创建一行两列的子图布局
fig, axes = plt.subplots(1, 2, figsize=(11, 4.2))

# ====== 1. 折线图 ======
x = np.linspace(0, 10, 100)
y = np.sin(x)
axes[0].plot(x, y, linewidth=2)
axes[0].set_title('折线图', fontsize=16)
axes[0].grid(True)

# ====== 2. 柱状图 ======
categories = ['A', 'B', 'C', 'D']
values = [20, 35, 30, 25]
axes[1].bar(categories, values, color='steelblue')
axes[1].set_title('柱状图', fontsize=16)

plt.tight_layout()
plt.show()
图中展示折线图与柱状图示例;读者应依据坐标、图例与注释比较主要模式。
Figure 2: 折线图与柱状图示例

扩展导览|关系与占比

展开散点图与饼图代码
fig, axes = plt.subplots(1, 2, figsize=(11, 4.2))

# ====== 3. 散点图 ======
x = np.random.randn(100)
y = np.random.randn(100)
axes[0].scatter(x, y, alpha=0.6)
axes[0].set_title('散点图', fontsize=16)
axes[0].set_xlabel('X', fontsize=16)
axes[0].set_ylabel('Y', fontsize=16)

# ====== 4. 饼图 ======
sizes = [30, 20, 25, 25]
labels = ['股票', '债券', '现金', '其他']
axes[1].pie(sizes, labels=labels, autopct='%1.1f%%', textprops={'fontsize':16})
axes[1].set_title('投资组合分布', fontsize=16)

plt.tight_layout()
plt.show()
图中展示散点图与饼图示例;读者应依据坐标、图例与注释比较主要模式。
Figure 3: 散点图与饼图示例

本章小结

  • Matplotlib 是 Python 数据可视化的核心基础库
  • 绘图标准流程:导入 → 数据 → 画布 → 绘图 → 设置 → 显示
  • 核心图表类型:折线图、柱状图、散点图、饼图
  • 重要元素:标题、轴标签、图例、网格、注释
  • 掌握 plt.subplots() 可灵活创建多子图布局

随堂练习

  • 问题 1|需要准备哪些数据?:贷款本金、年利率、贷款年限与等额本息公式。
  • 问题 2|需要完成哪些操作?:运行 lst-matplotlib-basic-calclst-matplotlib-basic-summaryfig-mortgage-repayment,逐月计算月供、本金、利息与余额。
  • 问题 3|应得到哪些结果?:月度构成表、累计利息、余额轨迹及带单位与图例的曲线。
  • 问题 4|怎样确认结果可靠?:检查每月本金加利息等于月供且末期余额归零;任一条件不满足时先检查原因。
  • 问题 5|换一个情境,怎样继续应用?:把年利率从4.9%拓展应用到6.0%,按同一公式比较月供、累计利息和余额轨迹。
  • 作答提示:请依次写清所用数据、分析过程、所得结果、核对方法和拓展思考。课程所需数据见前言中的下载入口;教学平台固定题按页面说明完成。

教师参考解答|答案与说明 1

  • 补充练习|等额本息月度推导、轴单位与图例

教师参考解答|代码 1

展开代码(代码区可独立滚动)
import numpy as np  # 导入数组计算库以执行等额本息推导
import pandas as pd  # 导入表格库以组织逐月时间序列
import matplotlib.pyplot as plt  # 导入绘图库以显示月度构成曲线
def amortize(principal,annual_rate,years):  # 定义同口径等额本息摊还函数
    months=years*12  # 把贷款年限换算为总月数
    monthly_rate=annual_rate/12  # 把年利率换算为月利率
    payment=principal*monthly_rate*(1+monthly_rate)**months/((1+monthly_rate)**months-1)  # 按标准公式计算固定月供
    balance=principal  # 初始化剩余本金
    rows=[]  # 初始化逐月检查记录
    for month in range(1,months+1):  # 逐月计算本金与利息构成
        interest=balance*monthly_rate  # 计算当月利息
        principal_paid=payment-interest if month<months else balance  # 末期用剩余本金消除浮点尾差
        balance=max(0.0,balance-principal_paid)  # 更新非负剩余本金
        rows.append((month,payment,principal_paid,interest,balance))  # 保存当月完整状态
    return pd.DataFrame(rows,columns=['月序','月供','本金','利息','剩余本金'])  # 返回标准化月度表
base=amortize(1_000_000,.049,30)  # 计算100万元、4.9%、30年基准情景
assert np.allclose(base['本金']+base['利息'],base['月供'],atol=1e-6) and np.isclose(base.iloc[-1]['剩余本金'],0.0)  # 核对组成守恒与余额归零
fig,axes=plt.subplots(1,2,figsize=(11,4.2))  # 创建构成与余额双面板
axes[0].plot(base['月序'],base['本金'],label='本金'); axes[0].plot(base['月序'],base['利息'],label='利息')  # 绘制逐月本金与利息
axes[1].plot(base['月序'],base['剩余本金'],label='4.9%余额',color='#007A86')  # 绘制基准余额轨迹
axes[0].set(xlabel='还款月份(月)',ylabel='每月金额(元)',title='等额本息月供构成'); axes[1].set(xlabel='还款月份(月)',ylabel='剩余本金(元)',title='余额轨迹')  # 标明轴、单位与图题
axes[0].legend(); axes[1].legend(); plt.tight_layout(); plt.show()  # 显示便于核对图例与曲线
print({'月供':round(base.iloc[0]['月供'],2),'累计利息':round(base['利息'].sum(),2),'末期余额':base.iloc[-1]['剩余本金']})  # 输出基准数值结果

教师参考解答|答案与说明 2

  • 解释与拓展应用答案:固定月供由当月本金与利息组成;随余额下降,利息递减、本金递增。第二情景必须复用同一公式和期限,不能手填构成序列。

  • 所用数据与字段:x/y 与单位、来源

  • 参考代码或推导(平台保护块之外):

教师参考解答|代码 2

展开代码(代码区可独立滚动)
higher=amortize(1_000_000,.060,30)  # 用同一本金与期限计算6.0%新案例情景
assert np.allclose(higher['本金']+higher['利息'],higher['月供'],atol=1e-6)  # 核对新案例情景逐月组成守恒
assert np.isclose(higher.iloc[-1]['剩余本金'],0.0)  # 核对高利率情景末期余额归零
assert higher['利息'].sum()>base['利息'].sum() and higher.iloc[0]['月供']>base.iloc[0]['月供']  # 检查高利率累计利息与月供更高
fig,ax=plt.subplots(figsize=(8.5,4.2))  # 创建两情景余额比较画布
ax.plot(base['月序'],base['剩余本金'],label='4.9%余额')  # 绘制基准余额轨迹
ax.plot(higher['月序'],higher['剩余本金'],label='6.0%余额')  # 绘制高利率余额轨迹
ax.set(xlabel='还款月份(月)',ylabel='剩余本金(元)',title='同本金同期限的利率情景比较')  # 保持统一轴与单位
ax.legend(title='年利率情景'); plt.tight_layout(); plt.show()  # 显示不遮挡曲线的情景图例
comparison=pd.DataFrame({'年利率':['4.9%','6.0%'],'月供':[base.iloc[0]['月供'],higher.iloc[0]['月供']],'累计利息':[base['利息'].sum(),higher['利息'].sum()],'末期余额':[base.iloc[-1]['剩余本金'],higher.iloc[-1]['剩余本金']]})  # 形成新案例核对表
print(comparison.round(2))  # 可见输出两情景月供、累计利息与余额

教师参考解答|答案与说明 3

  • 参考结果:月供、本金、利息、剩余本金月表;两情景月供/累计利息核对表及余额曲线
  • 边界 / 局限:本例假设固定利率、按月还款,且不考虑提前还款、费用与违约。每月本金与利息之和应等于月供,最后一期余额应接近 0。
  • 常见错误:把年利率直接当月利率;手填本金/利息;末期浮点尾差未清理;不同期限却比较利率